#pragma region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#pragma endregion

#pragma once

class DirectShowUtility
{
public:
	static HRESULT GuidFromString(const wchar_t* guidStr, GUID& guid)
	{
		if(guidStr == NULL) return E_POINTER;
		if(guidStr[0] != '{' && wcslen(guidStr) == 36)
		{
			CString str;
			str.Append(L"{");
			str.Append(guidStr, 36);
			str.Append(L"}");
			return CLSIDFromString(str.GetBuffer(), &guid);
		}
		else
			return CLSIDFromString(const_cast<wchar_t*>(guidStr), &guid);
	}

	static CString GuidToString(GUID& guid)
	{
		wchar_t buffer[50];
		if(StringFromGUID2(guid, buffer, SIZEOF_ARRAY(buffer)) == 0)
			return CString();
		else
			return CString(buffer);
	}

	template<class T>
	static HRESULT FindFilterById(IFilterGraph* filterGraph, const GUID& filterId, T** ppFilter)
	{
		CComPtr<IBaseFilter> baseFilter;
		HRESULT hr = FindFilterById(filterGraph, filterId, &baseFilter);
		if(FAILED(hr) || hr == S_FALSE) return hr;

		return baseFilter.QueryInterface(ppFilter);
	}

	static HRESULT FindFilterById(IFilterGraph* filterGraph, const GUID& filterId, IBaseFilter** ppFilter)
	{
		ATLASSERT(ppFilter != NULL);
		if(ppFilter == NULL) return E_POINTER;
		(*ppFilter) = NULL;

		HRESULT hr = S_OK;
		
		CComPtr<IEnumFilters> filters;
		hr = filterGraph->EnumFilters(&filters);
		if(FAILED(hr)) return hr;

		ULONG n;
		CComPtr<IBaseFilter> filter;
		while(filters->Next(1, &filter, &n) == S_OK)
		{
			CLSID nextFilterId;
			hr = filter->GetClassID(&nextFilterId);
			if(FAILED(hr)) break;

			if(nextFilterId == filterId)
			{
				(*ppFilter) = filter;
				(*ppFilter)->AddRef();
				break;
			}
			filter.Release();
		}

		if(FAILED(hr)) return hr;
		return (*ppFilter) == NULL ? S_FALSE : S_OK;
	}

	static HRESULT FindFilterByIUnknown(IFilterGraph* filterGraph, IUnknown* filterIUnknown, IBaseFilter** ppFilter)
	{
		ATLASSERT(ppFilter != NULL);
		if(ppFilter == NULL) return E_POINTER;
		(*ppFilter) = NULL;

		HRESULT hr = S_OK;
		
		CComPtr<IEnumFilters> filters;
		hr = filterGraph->EnumFilters(&filters);
		if(FAILED(hr)) return hr;

		ULONG n;
		CComPtr<IBaseFilter> filter;
		while(filters->Next(1, &filter, &n) == S_OK)
		{
			CComPtr<IUnknown> nextFilterIUnknown;
			hr = filter->QueryInterface(&nextFilterIUnknown);
			if(FAILED(hr)) break;

			if(nextFilterIUnknown == filterIUnknown)
			{
				(*ppFilter) = filter;
				(*ppFilter)->AddRef();
				break;
			}
			filter.Release();
		}

		if(FAILED(hr)) return hr;
		return (*ppFilter) == NULL ? S_FALSE : S_OK;
	}

	static HRESULT FindFiltersById(IFilterGraph* filterGraph, const GUID& filterId, CSimpleArray<CComPtr<IBaseFilter>>& foundFilters)
	{
		HRESULT hr = S_OK;
		
		CComPtr<IEnumFilters> filters;
		hr = filterGraph->EnumFilters(&filters);
		if(FAILED(hr)) return hr;

		ULONG n;
		CComPtr<IBaseFilter> filter;
		while(filters->Next(1, &filter, &n) == S_OK)
		{
			CLSID nextFilterId;
			hr = filter->GetClassID(&nextFilterId);
			if(FAILED(hr)) break;

			if(nextFilterId == filterId)
				foundFilters.Add(filter);

			filter.Release();
		}

		if(FAILED(hr)) return hr;
		return foundFilters.GetSize() > 0 ? S_OK : S_FALSE;
	}

	static HRESULT FilterExists(IFilterGraph* filterGraph, IUnknown* filterIUnknown)
	{
		CComPtr<IBaseFilter> baseFilter;
		return FindFilterByIUnknown(filterGraph, filterIUnknown, &baseFilter);
	}

	static HRESULT FilterExists(IFilterGraph* filterGraph, const GUID& filterId, IBaseFilter* exceptFilter = NULL)
	{
		ATLASSERT(filterGraph != NULL);
		if(filterGraph == NULL) return E_POINTER;

		HRESULT hr = S_OK;
		
		CComPtr<IEnumFilters> filters;
		hr = filterGraph->EnumFilters(&filters);
		if(FAILED(hr)) return hr;

		CComPtr<IUnknown> exceptUnknown;
		if(exceptFilter != NULL)
			hr = exceptFilter->QueryInterface(&exceptUnknown);
		if(FAILED(hr)) return hr;

		bool found = false;
		ULONG n;
		CComPtr<IBaseFilter> filter;
		while(filters->Next(1, &filter, &n) == S_OK)
		{
			CLSID nextFilterId;
			hr = filter->GetClassID(&nextFilterId);
			if(FAILED(hr)) break;

			if(nextFilterId == filterId)
			{
				if(exceptUnknown == NULL)
				{
					found = true;
					break;
				}

				CComPtr<IUnknown> nextFilterUnknown;
				hr = filter->QueryInterface(&nextFilterUnknown);
				if(FAILED(hr)) break;

				if(nextFilterUnknown != exceptUnknown)
				{
					found = true;
					break;
				}
			}
			filter.Release();
			filter.Detach();
		}

		if(FAILED(hr)) return hr;
		return found ? S_OK : S_FALSE;
	}

	static HRESULT FindUnconnectedPin(IBaseFilter* filter, PIN_DIRECTION direction, IPin** unconnectedPin)
	{
		ATLASSERT(filter != NULL);
		if(filter == NULL || unconnectedPin == NULL) return E_POINTER;

		HRESULT hr = S_OK;
		(*unconnectedPin) = NULL;

		CComPtr<IEnumPins> pins;
		hr = filter->EnumPins(&pins);
	    if(FAILED(hr)) return hr;

		DWORD n;
		CComPtr<IPin> pin;
		while(pins->Next(1, &pin, &n) == S_OK)
		{
			CComPtr<IPin> pinConnected;
			hr = pin->ConnectedTo(&pinConnected);
			if(hr == VFW_E_NOT_CONNECTED)
			{
				PIN_INFO pinInfo;
				hr = pin->QueryPinInfo(&pinInfo);
				if(FAILED(hr)) break;

				if(pinInfo.pFilter)
					pinInfo.pFilter->Release();

				if(pinInfo.dir == direction)
				{
					(*unconnectedPin) = pin;
					(*unconnectedPin)->AddRef();
					break;
				}
			}
			else
				if(FAILED(hr)) break;

			pin.Release();
			pin.Detach();
		}

		if(FAILED(hr)) return hr;
		return (*unconnectedPin) == NULL ? S_FALSE : S_OK;
	}

	static HRESULT GetNumberOfPins(IBaseFilter* filter, DWORD* numberOfPins, DWORD* numberOfConnectedPins)
	{
		ATLASSERT(filter != NULL);
		if(filter == NULL) return E_POINTER;

		HRESULT hr = S_OK;
		if(numberOfPins != NULL)
			*numberOfPins = 0;
		if(numberOfConnectedPins != NULL)
			*numberOfConnectedPins = 0;
	    
		CComPtr<IEnumPins> pins;
		hr = filter->EnumPins(&pins);
	    if(FAILED(hr)) return hr;

		DWORD n;
		CComPtr<IPin> pin;
		while(pins->Next(1, &pin, &n) == S_OK)
		{
			CComPtr<IPin> pinConnected;
			hr = pin->ConnectedTo(&pinConnected);
			if(hr == VFW_E_NOT_CONNECTED) hr = S_OK;
		    if(FAILED(hr)) break;

			if(pinConnected != NULL && numberOfConnectedPins != NULL)
				(*numberOfConnectedPins)++;

			if(numberOfPins != NULL)
				(*numberOfPins)++;

			pin.Release();
			pin.Detach();
		}

		return hr;
	}

	static HRESULT SaveConnections(IBaseFilter* filter, ConnectionInfoCollection& connections)
	{
		if(filter == NULL) return E_POINTER;

		HRESULT hr = S_OK;
	    
		CComPtr<IEnumPins> pins;
		hr = filter->EnumPins(&pins);
	    if(FAILED(hr)) return hr;

		connections.RemoveAll();

		DWORD n;
		CComPtr<IPin> pin;
		while(pins->Next(1, &pin, &n) == S_OK)
		{
			CComPtr<IPin> pinConnected;
			hr = pin->ConnectedTo(&pinConnected);
		    if(FAILED(hr)) break;

			if(pinConnected != NULL)
			{
				PIN_INFO pinInfo;
				hr = pin->QueryPinInfo(&pinInfo);
				if(FAILED(hr)) break;

				if(pinInfo.pFilter)
					pinInfo.pFilter->Release();

				ConnectionInfo info(pinInfo.dir,
					(pinInfo.dir == PINDIR_INPUT) ? pinConnected : pin,
					(pinInfo.dir == PINDIR_INPUT) ? pin : pinConnected);
				
				connections.Add(info);
			}
			pin.Release();
			pin.Detach();
		}

		if(FAILED(hr))
			connections.RemoveAll();

		return hr;
	}

	static HRESULT RestoreConnections(IFilterGraph* filterGraph, ConnectionInfoCollection& connections)
	{
		if(filterGraph == NULL) return E_POINTER;

		HRESULT hr = S_OK;
		for(int i = 0; i < connections.GetSize(); i++)
		{
			hr = filterGraph->ConnectDirect(connections[i].outPin, connections[i].inPin, NULL);
			if(FAILED(hr)) break;
		}
		return hr;
	}

	static HRESULT Disconnect(IFilterGraph* filterGraph, ConnectionInfoCollection& connections)
	{
		if(filterGraph == NULL) return E_POINTER;

		HRESULT hr = S_OK;
		for(int i = 0; i < connections.GetSize(); i++)
		{
			hr = filterGraph->Disconnect(connections[i].inPin);
			if(FAILED(hr)) break;

			hr = filterGraph->Disconnect(connections[i].outPin);
			if(FAILED(hr)) break;
		}
		return hr;
	}

	static HRESULT GetPinOwner(IPin* pin, IBaseFilter** filter)
	{
		if(pin == NULL) return E_POINTER;
		if(filter == NULL) return E_POINTER;
		*filter = NULL;

		PIN_INFO pinInfo = {0};
		HRESULT hr = pin->QueryPinInfo(&pinInfo);
		if(FAILED(hr)) return hr;
		if(pinInfo.pFilter == NULL) return E_UNEXPECTED;

		*filter = pinInfo.pFilter; // Skip AddRef + Release
		return S_OK;
	}

	static HRESULT CheckFilterClassID(IBaseFilter* filter, const GUID& filterClassID)
	{
		if(filter == NULL) return E_POINTER;
		CLSID clsid;
		HRESULT hr = filter->GetClassID(&clsid);
		if(FAILED(hr)) return hr;
		if(filterClassID == clsid)
			return S_OK;
		else
			return S_FALSE;
	}

	static HRESULT InsertFilter(IFilterGraph* filterGraph, const GUID& filterGuid,
		const GUID& afterFilterGuid, const GUID& beforeFilterGuid, CSimpleArray<CComPtr<IBaseFilter>>& insertedFilters)
	{
		CSimpleArray<CComPtr<IBaseFilter>> filters;
		HRESULT hr = DirectShowUtility::FindFiltersById(filterGraph, beforeFilterGuid, filters);
		if(FAILED(hr) || hr == S_FALSE) return hr;

		for(int f = 0; f < filters.GetSize(); f++)
		{
			CComPtr<IBaseFilter>& beforeFilter = filters[f];

			DWORD numberOfPins = 0;
			DWORD numberOfConnectedPins = 0;
			hr = DirectShowUtility::GetNumberOfPins(beforeFilter, &numberOfPins, &numberOfConnectedPins);
			if(FAILED(hr)) return hr;
			if(numberOfPins < 1) return E_UNEXPECTED;
			if(numberOfPins != numberOfConnectedPins) continue;

			ConnectionInfoCollection beforeFilterConnections;
			hr = DirectShowUtility::SaveConnections(beforeFilter, beforeFilterConnections);
			if(FAILED(hr)) return hr;
			if(beforeFilterConnections.GetSize() <= 0) return E_UNEXPECTED;

			for(int i = 0; i < beforeFilterConnections.GetSize(); i++)
			{
				if(beforeFilterConnections[i].direction != PINDIR_INPUT)
					continue;

				CComPtr<IPin> outPin;
				CComPtr<IPin> inPin;
				outPin = beforeFilterConnections[i].outPin;
				inPin = beforeFilterConnections[i].inPin;
				
				CComPtr<IBaseFilter> afterFilter;
				hr = DirectShowUtility::GetPinOwner(outPin, &afterFilter);
				if(FAILED(hr)) return hr;

				hr = DirectShowUtility::CheckFilterClassID(afterFilter, afterFilterGuid);
				if(FAILED(hr)) return hr;
				if(hr != S_OK) continue;

				CComPtr<IBaseFilter> filter;
				hr = filter.CoCreateInstance(filterGuid, NULL, CLSCTX_INPROC_SERVER);
				if(FAILED(hr)) return hr;

				CString filterName;
				filterName.Append(L"Inserted Filter ");
				filterName.AppendFormat(L"%08X", filter.p);

				hr = filterGraph->AddFilter(filter, filterName);
				if(FAILED(hr)) return hr;

				CComPtr<IPin> filterInputPin;
				hr = DirectShowUtility::FindUnconnectedPin(filter, PINDIR_INPUT, &filterInputPin);
				if(FAILED(hr)) return hr;

				// Disconnect an existing connection
				hr = outPin->Disconnect();
				if(FAILED(hr)) return hr;

				hr = inPin->Disconnect();
				if(FAILED(hr)) return hr;

				// Create a new input connection
				hr = filterGraph->ConnectDirect(outPin, filterInputPin, NULL);
				if(FAILED(hr)) return hr;

				CComPtr<IPin> filterOutputPin;
				hr = DirectShowUtility::FindUnconnectedPin(filter, PINDIR_OUTPUT, &filterOutputPin);
				if(FAILED(hr)) return hr;

				hr = filterGraph->ConnectDirect(filterOutputPin, inPin, NULL);
				if(FAILED(hr)) return hr;

				insertedFilters.Add(filter);

				ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
					"DirectShowUtility::InsertFilter() : "
					"Filter(0x%08X, outPin=0x%08X) -> NewFilter(0x%08X, inPin=0x%08X, outPin=0x%08X) -> Filter(0x%08X, inPin=0x%08X)\r\n",
					static_cast<IBaseFilter*>(afterFilter), outPin.p,
					static_cast<IBaseFilter*>(filter), filterInputPin.p, filterOutputPin.p,
					static_cast<IBaseFilter*>(beforeFilter), inPin.p);
			}
		}

		return S_OK;
	}

	static HRESULT BypassFilter(IFilterGraph* filterGraph, const GUID& filterId, bool removeFilter)
	{
		CComPtr<IBaseFilter> filter;
		HRESULT hr = DirectShowUtility::FindFilterById(filterGraph, filterId, &filter);
		if(FAILED(hr) || hr == S_FALSE) return hr;

		return DirectShowUtility::BypassFilter(filterGraph, filter, removeFilter);
	}

	static HRESULT BypassFilter(IFilterGraph* filterGraph, IBaseFilter* filter, bool removeFilter)
	{
		DWORD numberOfPins = 0;
		DWORD numberOfConnectedPins = 0;
		HRESULT hr = DirectShowUtility::GetNumberOfPins(filter, &numberOfPins, &numberOfConnectedPins);
		if(FAILED(hr)) return hr;
		if(numberOfPins != numberOfConnectedPins) return S_FALSE;
		if(numberOfPins != 2) return E_UNEXPECTED;

		ConnectionInfoCollection connections;
		hr = DirectShowUtility::SaveConnections(filter, connections);
		if(FAILED(hr)) return hr;
		if(connections.GetSize() != 2) return E_UNEXPECTED;

		ConnectionInfo& inConnection = (connections[0].direction == PINDIR_INPUT ? connections[0] : connections[1]);
		ConnectionInfo& outConnection = (connections[0].direction == PINDIR_INPUT ? connections[1] : connections[0]);

		ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
			"DirectShowUtility::BypassFilter() : inConnection(0x%08X, 0x%08X) -> Filter -> outConnection(0x%08X, 0x%08X)\r\n",
			inConnection.outPin.p, inConnection.inPin.p,
			outConnection.outPin.p, outConnection.inPin.p);

		hr = filterGraph->Disconnect(inConnection.outPin);
		//hr = inConnection.outPin->Disconnect();
		if(FAILED(hr)) return hr;

		hr = outConnection.inPin->Disconnect();
		if(FAILED(hr)) return hr;

		if(removeFilter)
		{
			hr = filterGraph->RemoveFilter(filter);
			if(FAILED(hr)) return hr;
		}

		hr = filterGraph->ConnectDirect(inConnection.outPin, outConnection.inPin, NULL);

		ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
			"DirectShowUtility::BypassFilter() : new connection(0x%08X, 0x%08X) - 0x%08X\r\n",
			inConnection.outPin.p, outConnection.inPin.p, hr);

		if(FAILED(hr)) return hr;

		return S_OK;
	}

#ifdef _DEBUG
	static void DumpDisplayType(LPTSTR label, const AM_MEDIA_TYPE* pmtIn)
	{
		/* Dump the GUID types and a short description */

		ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "\r\n");
		ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "%s  M type %hs  S type %hs\r\n",
			label, GuidNames[pmtIn->majortype], GuidNames[pmtIn->subtype]);
		ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Subtype description %s\r\n",
			GetSubtypeName(&pmtIn->subtype));

		/* Dump the generic media types */
		if(pmtIn->bTemporalCompression)
		{
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Temporally compressed\r\n");
		}
		else
		{
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Not temporally compressed\r\n");
		}

		if(pmtIn->bFixedSizeSamples)
		{
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Sample size %d\r\n", pmtIn->lSampleSize);
		}
		else
		{
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Variable size samples\r\n");
		}

		if(pmtIn->formattype == FORMAT_VideoInfo)
		{
			/* Dump the contents of the BITMAPINFOHEADER structure */
			BITMAPINFOHEADER *pbmi = HEADER(pmtIn->pbFormat);
			VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *)pmtIn->pbFormat;

			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
				"Source rectangle (Left %d Top %d Right %d Bottom %d)\r\n",
				pVideoInfo->rcSource.left, pVideoInfo->rcSource.top,
				pVideoInfo->rcSource.right, pVideoInfo->rcSource.bottom);

			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
				"Target rectangle (Left %d Top %d Right %d Bottom %d)\r\n",
				pVideoInfo->rcTarget.left, pVideoInfo->rcTarget.top,
				pVideoInfo->rcTarget.right, pVideoInfo->rcTarget.bottom);

			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
				"Size of BITMAPINFO structure %d\r\n", pbmi->biSize);
			if(pbmi->biCompression < 256)
			{
				ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "%dx%dx%d bit  (%d)\r\n",
					pbmi->biWidth, pbmi->biHeight, pbmi->biBitCount, pbmi->biCompression);
			}
			else
			{
				ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "%dx%dx%d bit '%4.4hs'\r\n",
					pbmi->biWidth, pbmi->biHeight, pbmi->biBitCount, &pbmi->biCompression);
			}

			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Image size %d\r\n", pbmi->biSizeImage);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Planes %d\r\n" , pbmi->biPlanes);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "X Pels per metre %d\r\n", pbmi->biXPelsPerMeter);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Y Pels per metre %d\r\n",pbmi->biYPelsPerMeter);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "Colours used %d\r\n", pbmi->biClrUsed);
		}
		else if(pmtIn->majortype == MEDIATYPE_Audio)
		{
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "     Format type %hs\r\n", GuidNames[pmtIn->formattype]);
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "     Subtype %hs\r\n", GuidNames[pmtIn->subtype]);

			if((pmtIn->subtype != MEDIASUBTYPE_MPEG1Packet) && (pmtIn->cbFormat >= sizeof(PCMWAVEFORMAT)))
			{
				/* Dump the contents of the WAVEFORMATEX type-specific format structure */
				WAVEFORMATEX *pwfx = (WAVEFORMATEX *) pmtIn->pbFormat;

				ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
					"wFormatTag %u, nChannels %u, nSamplesPerSec %lu, nAvgBytesPerSec %lu, nBlockAlign %u, wBitsPerSample %u\r\n",
					pwfx->wFormatTag, pwfx->nChannels, pwfx->nSamplesPerSec, pwfx->nAvgBytesPerSec,
					pwfx->nBlockAlign, pwfx->wBitsPerSample);

				/* PCM uses a WAVEFORMAT and does not have the extra size field */
				if(pmtIn->cbFormat >= sizeof(WAVEFORMATEX))
				{
					ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "cbSize %u", pwfx->cbSize);
				}
			}
		}
		else
		{
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "     Format type %hs\r\n", GuidNames[pmtIn->formattype]);
			// !!!! should add code to dump wave format, others
		}
	}

	static void DumpFilterGraph(IFilterGraph* pGraph)
	{
		if(!pGraph) return;

	    ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
			"  |------------------ Filter Graph [%08X] -----------------\r\n", pGraph);

		IEnumFilters* pFilters;
		if(FAILED(pGraph->EnumFilters(&pFilters)))
		{
			ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "EnumFilters failed!\r\n");
			return;
		}

		HRESULT hr;
		IBaseFilter* pFilter = NULL;
		ULONG n;
		int filterIndex = 0;
		while(pFilters->Next(1, &pFilter, &n) == S_OK)
		{
			if(filterIndex > 0)
			{
				ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "  |\r\n");
			}

			FILTER_INFO	info;
			if(FAILED(pFilter->QueryFilterInfo(&info)))
			{
				ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
					"  | %d Filter [%08X] -- failed QueryFilterInfo\r\n", filterIndex + 1, pFilter);
			}
			else
			{
				QueryFilterInfoReleaseGraph(info);
				// !!! should QueryVendorInfo here!
				
				CStringA extInfo;
				GetFilterExtInfo(pFilter, extInfo);
			
				wchar_t filterIdStr[50] = {0};
				CLSID filterId;
				hr = pFilter->GetClassID(&filterId);
				if(SUCCEEDED(hr))
					StringFromGUID2(filterId, filterIdStr, SIZEOF_ARRAY(filterIdStr));

				ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
					"  | %d Filter [%08X] '%ls' %S. %s\r\n", filterIndex + 1, pFilter, info.achName, filterIdStr, extInfo.GetBuffer());

				IEnumPins* pins;
				if(FAILED(pFilter->EnumPins(&pins)))
				{
					ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "EnumPins failed!\r\n");
				}
				else
				{
					IPin* pPin;
					while(pins->Next(1, &pPin, &n) == S_OK)
					{
						PIN_INFO info;
						if(FAILED(pPin->QueryPinInfo(&info)))
						{
							ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
								"  |       Pin [%08X]  -- failed QueryPinInfo\r\n", pPin);
						}
						else
						{
							QueryPinInfoReleaseFilter(info);

							IPin* pPinConnected = NULL;
							HRESULT hr = pPin->ConnectedTo(&pPinConnected);
							if(pPinConnected)
							{
								char connectionType[200] = {0};

								// perhaps we should really dump the type both ways as a sanity check?
								if(info.dir == PINDIR_OUTPUT)
								{
									AM_MEDIA_TYPE mt;
									hr = pPin->ConnectionMediaType(&mt);
									if(SUCCEEDED(hr))
									{
										sprintf_s(connectionType, _countof(connectionType), "- %hs / %hs",
											GuidNames[mt.majortype], GuidNames[mt.subtype]);

										FreeMediaType(mt);
									}
								}

								ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
									"  |       Pin [%08X] '%ls' [%sput] connected to pin [%08X] %s\r\n",
									pPin, info.achName, (info.dir == PINDIR_INPUT ? "In" : "Out"),
									pPinConnected, connectionType);

								pPinConnected->Release();
							}
							else
							{
								ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
									"  |       Pin [%08X] '%ls' [%sput]\r\n",
									pPin, info.achName, (info.dir == PINDIR_INPUT ? "In" : "Out"));
							}
						}
						pPin->Release();
					}
					pins->Release();
				}
			}
			pFilter->Release();

			filterIndex++;

		} // pFilters->Next()
		pFilters->Release();

		//CStringA videoInfo;
		//hr = GetBasicVideoInfo(pGraph, videoInfo);
		//if(SUCCEEDED(hr) && videoInfo.GetLength() > 0)
		//{
		//	ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "  |\r\n");
		//	ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "  | Video:\r\n");
		//	ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose, "  |       %s\r\n", videoInfo.GetBuffer());
		//}

		ATLTRACE_TS(atlTraceGeneral, TraceLevel::Verbose,
			"  |------------------------------------------------------------\r\n");
	}

#else
	static void DumpDisplayType(LPTSTR label, const AM_MEDIA_TYPE* pmtIn)
	{
		label;
		pmtIn;
	}
	
	static void DumpFilterGraph(IFilterGraph* pGraph)
	{
		pGraph;
	}
#endif

	static HRESULT GetFilterExtInfo(IBaseFilter* baseFilter, CStringA& info)
	{
		if(baseFilter == NULL) return E_POINTER;

		CComPtr<IVMRFilterConfig> vmrFilterConfig;
		HRESULT hr = baseFilter->QueryInterface(&vmrFilterConfig);
		if(FAILED(hr)) return hr;

		DWORD mode = 0;
		hr = vmrFilterConfig->GetRenderingMode(&mode);
		if(FAILED(hr)) return hr;

		DWORD flags = 0;
		hr = vmrFilterConfig->GetRenderingPrefs(&flags);
		if(FAILED(hr)) return hr;

		info.AppendFormat("Rendering mode: %d (%s), Prefs: 0x%X",
			mode, (mode == 1 ? "Windowed" : mode == 2 ? "Windowless" : mode == 4 ? "Renderless" : "Unknown"),
			flags);
		
		return S_OK;
	}

	static HRESULT GetBasicVideoInfo(IFilterGraph* filterGraph, CStringA& info)
	{
		if(filterGraph == NULL) return E_POINTER;

		long left, top, width, height;

		CComPtr<IBasicVideo> video;
		HRESULT hr = filterGraph->QueryInterface(&video);
		if(SUCCEEDED(hr))
		{
			hr = video->GetVideoSize(&width, &height);
			if(SUCCEEDED(hr))
				info.AppendFormat("Size(w:%d, h:%d) ", width, height);

			hr = video->GetSourcePosition(&left, &top, &width, &height);
			if(SUCCEEDED(hr))
				info.AppendFormat("Source(l:%d, t:%d, w:%d, h:%d) ", left, top, width, height);

			hr = video->GetDestinationPosition(&left, &top, &width, &height);
			if(SUCCEEDED(hr))
				info.AppendFormat("Dest(l:%d, t:%d, w:%d, h:%d) ", left, top, width, height);

			hr = video->IsUsingDefaultSource();
			if(SUCCEEDED(hr))
				info.AppendFormat("DefaultSource=%s ", hr == S_OK ? "yes" : "no");

			hr = video->IsUsingDefaultDestination();
			if(SUCCEEDED(hr))
				info.AppendFormat("DefaultDest=%s ", hr == S_OK ? "yes" : "no");
		}

		CComPtr<IBasicVideo2> video2;
		hr = filterGraph->QueryInterface(&video2);
		if(SUCCEEDED(hr))
		{
			hr = video2->GetPreferredAspectRatio(&width, &height);
			if(SUCCEEDED(hr))
				info.AppendFormat("Aspect=%d:%d ", width, height);
		}

		return S_OK;
	}
};








